/* 
 * ProfileTool
 * Copyright 2006-2008 Craig Box
 * Released under the Mozilla Public License v1.1
 * 
 * http://www.codeplex.com/ProfileTool
 */

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using Microsoft.Win32;
using System.Security.Principal;
using System.IO;
using CubicOrange.Net.DsObjectPickerWrapper;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.Win32.Security;
using System.DirectoryServices;
using System.Diagnostics;
using System.Threading;

namespace ProfileTool
{
	/// <summary>
	/// Summary description for ptMainDialog.
	/// </summary>
	public class ptMainDialog : System.Windows.Forms.Form
	{

		ArrayList ProfileList;
		delegate void FillSizesCallback(int index, string text);
		Thread FillSizesThread;
		private ListViewColumnSorter lvwColumnSorter;

		private System.Windows.Forms.MainMenu mainMenu1;
		private System.Windows.Forms.ContextMenu contextMenu1;
		private System.Windows.Forms.ColumnHeader columnHeader1;
		private System.Windows.Forms.ColumnHeader columnHeader2;
		private System.Windows.Forms.ColumnHeader columnHeader3;
		private System.Windows.Forms.ColumnHeader columnHeader4;
		private System.Windows.Forms.ColumnHeader columnHeader5;
		private System.Windows.Forms.ColumnHeader columnHeader6;
		private System.Windows.Forms.ColumnHeader columnHeader7;
		private System.Windows.Forms.ListView profileListView;
		private System.Windows.Forms.MenuItem menuItemProfileExit;
		private System.Windows.Forms.MenuItem menuChangeProfileOwner;
		private System.Windows.Forms.MenuItem menuRenameDirectory;
		private System.Windows.Forms.MenuItem menuDeleteProfile;
		private System.Windows.Forms.MenuItem menuDeleteFromRegistryOnly;
		private System.Windows.Forms.MenuItem menuDeleteFromRegistryAndDisk;
		private System.Windows.Forms.MenuItem menuLoadHive;
		private System.Windows.Forms.MenuItem menuAddRegistryPermission;
		private System.Windows.Forms.MenuItem menuRemoveRegistryPermission;
		private System.Windows.Forms.MenuItem menuAddFolderPermission;
		private System.Windows.Forms.MenuItem menuRemoveOwnerPermission;
		private System.Windows.Forms.MenuItem menuUnloadHive;
		private System.Windows.Forms.MenuItem menuHelpAbout;
		private System.Windows.Forms.MenuItem menuDebug;
		private System.Windows.Forms.MenuItem menuChangeRoamingLocal;
		private System.Windows.Forms.MenuItem menuProfile;
		private System.Windows.Forms.MenuItem menuHelp;
		private System.Windows.Forms.MenuItem menuHelpUsing;
		private System.Windows.Forms.MenuItem menuChangeLocalRoaming;
		private System.Windows.Forms.StatusBar ptFormStatusBar;
		/// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

		public ptMainDialog()
		{
			//
			// Required for Windows Form Designer support
			//
			Console.WriteLine("ProfileTool " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString() + " initializing.");
			InitializeComponent();
			
			HideDebugOptionsInReleaseMode();
			LoadProfileList();
			DisplayProfileList();
		}

		/// <summary>
		/// Hide the debug context menu items if you're running a binary compiled 
		/// in release mode.
		/// </summary>
		private void HideDebugOptionsInReleaseMode() 
		{
			menuDebug.Visible=false;
#if DEBUG
			menuDebug.Visible=true;		
#endif
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(ptMainDialog));
			this.contextMenu1 = new System.Windows.Forms.ContextMenu();
			this.menuChangeProfileOwner = new System.Windows.Forms.MenuItem();
			this.menuRenameDirectory = new System.Windows.Forms.MenuItem();
			this.menuDeleteProfile = new System.Windows.Forms.MenuItem();
			this.menuDeleteFromRegistryOnly = new System.Windows.Forms.MenuItem();
			this.menuDeleteFromRegistryAndDisk = new System.Windows.Forms.MenuItem();
			this.menuChangeRoamingLocal = new System.Windows.Forms.MenuItem();
			this.menuChangeLocalRoaming = new System.Windows.Forms.MenuItem();
			this.menuDebug = new System.Windows.Forms.MenuItem();
			this.menuLoadHive = new System.Windows.Forms.MenuItem();
			this.menuRemoveRegistryPermission = new System.Windows.Forms.MenuItem();
			this.menuAddRegistryPermission = new System.Windows.Forms.MenuItem();
			this.menuRemoveOwnerPermission = new System.Windows.Forms.MenuItem();
			this.menuAddFolderPermission = new System.Windows.Forms.MenuItem();
			this.menuUnloadHive = new System.Windows.Forms.MenuItem();
			this.mainMenu1 = new System.Windows.Forms.MainMenu();
			this.menuProfile = new System.Windows.Forms.MenuItem();
			this.menuItemProfileExit = new System.Windows.Forms.MenuItem();
			this.menuHelp = new System.Windows.Forms.MenuItem();
			this.menuHelpUsing = new System.Windows.Forms.MenuItem();
			this.menuHelpAbout = new System.Windows.Forms.MenuItem();
			this.profileListView = new System.Windows.Forms.ListView();
			this.columnHeader1 = new System.Windows.Forms.ColumnHeader();
			this.columnHeader2 = new System.Windows.Forms.ColumnHeader();
			this.columnHeader3 = new System.Windows.Forms.ColumnHeader();
			this.columnHeader4 = new System.Windows.Forms.ColumnHeader();
			this.columnHeader5 = new System.Windows.Forms.ColumnHeader();
			this.columnHeader6 = new System.Windows.Forms.ColumnHeader();
			this.columnHeader7 = new System.Windows.Forms.ColumnHeader();
			this.ptFormStatusBar = new System.Windows.Forms.StatusBar();
			this.SuspendLayout();
			// 
			// contextMenu1
			// 
			this.contextMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
																						 this.menuChangeProfileOwner,
																						 this.menuRenameDirectory,
																						 this.menuDeleteProfile,
																						 this.menuChangeRoamingLocal,
																						 this.menuChangeLocalRoaming,
																						 this.menuDebug});
			// 
			// menuChangeProfileOwner
			// 
			this.menuChangeProfileOwner.Index = 0;
			this.menuChangeProfileOwner.Text = "Change profile owner";
			this.menuChangeProfileOwner.Click += new System.EventHandler(this.menuChangeProfileOwner_Click);
			// 
			// menuRenameDirectory
			// 
			this.menuRenameDirectory.Index = 1;
			this.menuRenameDirectory.Text = "Rename directory";
			this.menuRenameDirectory.Click += new System.EventHandler(this.menuRenameDirectory_Click);
			// 
			// menuDeleteProfile
			// 
			this.menuDeleteProfile.Index = 2;
			this.menuDeleteProfile.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
																							  this.menuDeleteFromRegistryOnly,
																							  this.menuDeleteFromRegistryAndDisk});
			this.menuDeleteProfile.Text = "Delete this profile";
			// 
			// menuDeleteFromRegistryOnly
			// 
			this.menuDeleteFromRegistryOnly.Index = 0;
			this.menuDeleteFromRegistryOnly.Text = "Delete from registry only";
			this.menuDeleteFromRegistryOnly.Click += new System.EventHandler(this.menuDeleteFromRegistryOnly_Click);
			// 
			// menuDeleteFromRegistryAndDisk
			// 
			this.menuDeleteFromRegistryAndDisk.Index = 1;
			this.menuDeleteFromRegistryAndDisk.Text = "Delete from registry and disk";
			this.menuDeleteFromRegistryAndDisk.Click += new System.EventHandler(this.menuDeleteFromRegistryAndDisk_Click);
			// 
			// menuChangeRoamingLocal
			// 
			this.menuChangeRoamingLocal.Index = 3;
			this.menuChangeRoamingLocal.Text = "Change from roaming to local";
			this.menuChangeRoamingLocal.Click += new System.EventHandler(this.menuChangeRoamingLocal_Click);
			// 
			// menuChangeLocalRoaming
			// 
			this.menuChangeLocalRoaming.Index = 4;
			this.menuChangeLocalRoaming.Text = "Change from local to roaming";
			this.menuChangeLocalRoaming.Click += new System.EventHandler(this.menuChangeLocalRoaming_Click);
			// 
			// menuDebug
			// 
			this.menuDebug.Index = 5;
			this.menuDebug.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
																					  this.menuLoadHive,
																					  this.menuRemoveRegistryPermission,
																					  this.menuAddRegistryPermission,
																					  this.menuRemoveOwnerPermission,
																					  this.menuAddFolderPermission,
																					  this.menuUnloadHive});
			this.menuDebug.Text = "Debug";
			// 
			// menuLoadHive
			// 
			this.menuLoadHive.Index = 0;
			this.menuLoadHive.Text = "Load profile to HKEY_USERS";
			this.menuLoadHive.Click += new System.EventHandler(this.menuLoadHive_Click);
			// 
			// menuRemoveRegistryPermission
			// 
			this.menuRemoveRegistryPermission.Enabled = false;
			this.menuRemoveRegistryPermission.Index = 1;
			this.menuRemoveRegistryPermission.Text = "Remove owner registry permission";
			this.menuRemoveRegistryPermission.Click += new System.EventHandler(this.menuRemoveRegistryPermission_Click);
			// 
			// menuAddRegistryPermission
			// 
			this.menuAddRegistryPermission.Enabled = false;
			this.menuAddRegistryPermission.Index = 2;
			this.menuAddRegistryPermission.Text = "Add registry permission";
			this.menuAddRegistryPermission.Click += new System.EventHandler(this.menuAddRegistryPermission_Click);
			// 
			// menuRemoveOwnerPermission
			// 
			this.menuRemoveOwnerPermission.Index = 3;
			this.menuRemoveOwnerPermission.Text = "Remove owner folder permission";
			this.menuRemoveOwnerPermission.Click += new System.EventHandler(this.menuRemoveOwnerPermission_Click);
			// 
			// menuAddFolderPermission
			// 
			this.menuAddFolderPermission.Index = 4;
			this.menuAddFolderPermission.Text = "Add folder permission";
			this.menuAddFolderPermission.Click += new System.EventHandler(this.menuAddFolderPermission_Click);
			// 
			// menuUnloadHive
			// 
			this.menuUnloadHive.Enabled = false;
			this.menuUnloadHive.Index = 5;
			this.menuUnloadHive.Text = "Unload profile from registry";
			this.menuUnloadHive.Click += new System.EventHandler(this.menuUnloadHive_Click);
			// 
			// mainMenu1
			// 
			this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
																					  this.menuProfile,
																					  this.menuHelp});
			// 
			// menuProfile
			// 
			this.menuProfile.Index = 0;
			this.menuProfile.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
																						this.menuItemProfileExit});
			this.menuProfile.Text = "&Profile";
			// 
			// menuItemProfileExit
			// 
			this.menuItemProfileExit.Index = 0;
			this.menuItemProfileExit.Text = "E&xit";
			this.menuItemProfileExit.Click += new System.EventHandler(this.menuItemProfileExit_Click);
			// 
			// menuHelp
			// 
			this.menuHelp.Index = 1;
			this.menuHelp.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
																					 this.menuHelpUsing,
																					 this.menuHelpAbout});
			this.menuHelp.Text = "&Help";
			// 
			// menuHelpUsing
			// 
			this.menuHelpUsing.Index = 0;
			this.menuHelpUsing.Text = "&Using";
			this.menuHelpUsing.Click += new System.EventHandler(this.menuHelpUsing_Click);
			// 
			// menuHelpAbout
			// 
			this.menuHelpAbout.Index = 1;
			this.menuHelpAbout.Text = "&About";
			this.menuHelpAbout.Click += new System.EventHandler(this.menuHelpAbout_Click);
			// 
			// profileListView
			// 
			this.profileListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
																							  this.columnHeader1,
																							  this.columnHeader2,
																							  this.columnHeader3,
																							  this.columnHeader4,
																							  this.columnHeader5,
																							  this.columnHeader6,
																							  this.columnHeader7});
			this.profileListView.Dock = System.Windows.Forms.DockStyle.Fill;
			this.profileListView.FullRowSelect = true;
			this.profileListView.Location = new System.Drawing.Point(0, 0);
			this.profileListView.MultiSelect = false;
			this.profileListView.Name = "profileListView";
			this.profileListView.Size = new System.Drawing.Size(720, 176);
			this.profileListView.TabIndex = 0;
			this.profileListView.View = System.Windows.Forms.View.Details;
			this.profileListView.MouseDown += new System.Windows.Forms.MouseEventHandler(this.profileListView_MouseDown);
			this.profileListView.ColumnClick += new System.Windows.Forms.ColumnClickEventHandler(this.profileListView_ColumnClick);
			// 
			// columnHeader1
			// 
			this.columnHeader1.Text = "Name";
			this.columnHeader1.Width = 150;
			// 
			// columnHeader2
			// 
			this.columnHeader2.Text = "Size";
			this.columnHeader2.Width = 75;
			// 
			// columnHeader3
			// 
			this.columnHeader3.Text = "Type";
			this.columnHeader3.Width = 55;
			// 
			// columnHeader4
			// 
			this.columnHeader4.Text = "Status";
			this.columnHeader4.Width = 50;
			// 
			// columnHeader5
			// 
			this.columnHeader5.Text = "Modified";
			this.columnHeader5.Width = 80;
			// 
			// columnHeader6
			// 
			this.columnHeader6.Text = "Path";
			this.columnHeader6.Width = 250;
			// 
			// columnHeader7
			// 
			this.columnHeader7.Text = "SID";
			this.columnHeader7.Width = 300;
			// 
			// ptFormStatusBar
			// 
			this.ptFormStatusBar.Location = new System.Drawing.Point(0, 154);
			this.ptFormStatusBar.Name = "ptFormStatusBar";
			this.ptFormStatusBar.Size = new System.Drawing.Size(720, 22);
			this.ptFormStatusBar.TabIndex = 1;
			this.ptFormStatusBar.Text = "statusBar1";
			// 
			// ptMainDialog
			// 
			this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
			this.ClientSize = new System.Drawing.Size(720, 176);
			this.Controls.Add(this.ptFormStatusBar);
			this.Controls.Add(this.profileListView);
			this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
			this.Menu = this.mainMenu1;
			this.Name = "ptMainDialog";
			this.Text = "ProfileTool";
			this.ResumeLayout(false);

		}
		#endregion

		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main() 
		{
			if (!IsAdministrator)
			{
				MessageBox.Show("You must be an administrator to run ProfileTool.", "ProfileTool");
				Environment.Exit(255);
			}

			if (!IsWindows2000Plus)
			{
				MessageBox.Show("ProfileTool requires Windows 2000 or greater.", "ProfileTool");
				Environment.Exit(255);
			}

			Application.EnableVisualStyles();
			Application.Run(new ptMainDialog());
		}

		/// <summary>
		/// Establish whether or not the currently logged in user is an administrator
		/// </summary>
		private static bool IsAdministrator
		{
			get
			{
				if (SystemInformation.Secure)
				{
					WindowsIdentity identity = WindowsIdentity.GetCurrent();
					WindowsPrincipal principal = new WindowsPrincipal (identity);
					return principal.IsInRole (WindowsBuiltInRole.Administrator);
				}
				return true;
			}
		}

		/// <summary>
		/// Establish if the current OS is Windows 2000 or greater.
		/// </summary>
		private static bool IsWindows2000Plus
		{
			get
			{
				OperatingSystem os = Environment.OSVersion;
				Version vs = os.Version;
				if (vs.Major >= 5)
					return true;
				else
					return false;
			}
		}


		/// <summary>
		/// Populate the ProfileList array of Profile objects from 
		/// HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList.
		/// </summary>
		public void LoadProfileList()
		{
			ProfileList = new ArrayList();

			RegistryKey regKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList");        

			// All the keys under this key are profiles.  Enumerate each one...
			foreach (string Keyname in regKey.GetSubKeyNames())
			{
				// ignoring the S-1-5-1x keys, which are profiles for system accounts
				if (Keyname.Length > 8)
				{
					// Create a profile object for this profile.
					Profile pr = new Profile(Keyname);
                    
					// pull the path on the disc from the registry
					RegistryKey pipRegKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\" + Keyname);
					pr.Path = pipRegKey.GetValue("ProfileImagePath").ToString();

					// Find the age of the profile.  This is done from the 
					// ntuser.ini file in the profile directory.
					FileInfo f = new FileInfo(pr.Path + @"\ntuser.ini");
					pr.Modified = f.LastWriteTime;

					// get state
					// TODO: fail cleanly if "state" not present.
					pr.State = (int) pipRegKey.GetValue("State"); 

					// get central path (set blank if it's not a roaming profile)
					try
					{
						pr.RoamingPath = pipRegKey.GetValue("CentralProfile").ToString();
					}
					catch
					{
						pr.RoamingPath = "";
					}

					// get user preferences - returns -1 if key doesn't exist
					pr.UserPreference = (int)pipRegKey.GetValue("UserPreference", -1);
					
					/* for a profile to be roamable but local:
					 * - centralimagepath must exist
					 * - UserPreference must not be set to -1, if it exists.
					 * 
					 * */


					// add it to to the list
					ProfileList.Add(pr);
				}
			}
		}

		/// <summary>
		/// Take the ProfileList and display it in the ListView.
		/// </summary>
		public void DisplayProfileList()
		{
			
			// Create an instance of a ListView column sorter and assign it 
			// to the ListView control.
			lvwColumnSorter = new ListViewColumnSorter();
			profileListView.ListViewItemSorter = lvwColumnSorter;

			profileListView.Items.Clear();
			foreach (Profile pr in ProfileList)
			{
				// order of display: name, total size, type, 
				// status, modified, path, sid
				profileListView.Items.Add(pr.Username);
				// size isn't filled out here - it's done in the background
				// by the FillSizes thread below
				int index = profileListView.Items.Count - 1;
				profileListView.Items[index].SubItems.Add("Calculating..."); // size
				profileListView.Items[index].SubItems.Add(getType(pr.State, pr.UserPreference, pr.RoamingPath)); 
				profileListView.Items[index].SubItems.Add(getStatus(pr.State, pr.RoamingPath));
				profileListView.Items[index].SubItems.Add(pr.Modified.ToShortDateString());
				profileListView.Items[index].SubItems.Add(pr.Path);
				profileListView.Items[index].SubItems.Add(pr.SID);
			}
			
			ptFormStatusBar.Text="Ready.";

			FillSizesThread = new Thread(new ThreadStart(this.FillSizes));
			FillSizesThread.Start();
		}


		/*
			 * The mask bits and their meaning are:

					Hex Mask    Description
					 0001       Profile is mandatory.
					 0002       Update the locally cached profile.
					 0004       New local profile.
					 0008       New central profile.
					 0010       Update the central profile.
					 0020       Delete the cached profile.
					 0040       Upgrade the profile.
					 0080       Using Guest user profile.
					 0100       Using Administrator profile.
					 0200       Default net profile is available and ready.
					 0400       Slow network link identified.
					 0800       Temporary profile loaded.
         
			 */
                  
		/// <summary>
		/// Get the type of the profile.
		/// </summary>
		private string getType(int state, int pref, string path) 
		{
			// possible states: Mandatory, Local, Roaming.
			// state & 0x1 always generates Mandatory/Mandatory
			if ((state & 0x1) == 0x1)
				return "Mandatory";
			// the "Roaming" in the "Type" column refers to if a CentralProfile is set,
			// and the state of UserPreference.  1 or unset means 'roaming', 0 means local.
			//
			// http://www.microsoft.com/technet/archive/winntas/maintain/profiles.mspx?mfr=true

			if (path.Length > 0 && (pref == 1 || pref == -1))
				return "Roaming";
			else
				return "Local";
		}

		/// <summary>
		/// Get the state of the profile.
		/// </summary>
		private string getStatus(int state, string path) 
		{
			// state & 0x1 always generates Mandatory/Mandatory
			if ((state & 0x1) == 0x1)
				return "Mandatory";
			if (((state & 0x800) == 0x800 ) || ((state & 0x80) == 0x80))
				return "Temporary";
			if ((state & 0x10) == 0x10)
				return "Roaming";
			else
				return "Local";
		}

		/// <summary>
		/// Convert a file size in bytes to a friendly string representation.
		/// Found at http://tommycarlier.blogspot.com/2006/05/format-file-size.html.
		/// </summary>
		public static string FormatFileSize(long fileSize)
		{
			if (fileSize < 0) throw new ArgumentOutOfRangeException("fileSize");

			if (fileSize >= 1024 * 1024 * 1024) return string.Format("{0:########0.00} GB", ((double)fileSize) / (1024 * 1024 * 1024));
			else if (fileSize >= 1024 * 1024) return string.Format("{0:####0.00} MB", ((double)fileSize) / (1024 * 1024));
			else if (fileSize >= 1024) return string.Format("{0:####0.00} KB", ((double)fileSize) / 1024);
			else return string.Format("{0} bytes", fileSize);
		}

		/// <summary>
		/// Convert a connection string (eg. ldap://domain/user) to a NT 
		/// username (domain\user).
		/// </summary>
		private string ConvertLDAPToNTName(string dn)
		{
			/* returned from the ObjectPicker, we can have one of three options:
				 *  ldap://foo/blah
				 *    gc://foo:3268/blah
				 * WinNT://domain/machine/username
				 * 
				 * We need to deal with the WinNT case separately, and strip the URL
				 * parameters from the other two.
				 */

			// local account - strip domain and change slash direction,
			// no further lookup is required
			if (dn.ToLower().StartsWith("winnt://"))
			{
				dn = dn.Substring(8);
				dn = dn.Substring(dn.IndexOf(@"/") + 1);
				dn = dn.Replace (@"/", @"\");
				return dn;
			}

			// LDAP account - use NameTranslate to get us the NT name.
			if (dn.ToLower().StartsWith("ldap://"))
			{
				dn = dn.Substring(7);
				dn = dn.Substring(dn.IndexOf(@"/") + 1);
			}

			if (dn.ToLower().StartsWith("gc://"))
			{
				dn = dn.Substring(5);
				dn = dn.Substring(dn.IndexOf(@"/") + 1);
			}

			// The following code requires ActiveDS.dll as a reference.
			// see http://www.rlmueller.net/NameTranslateFAQ.htm for more.
			ActiveDs.NameTranslateClass trans = new ActiveDs.NameTranslateClass();

			trans.Init((int)ActiveDs.ADS_NAME_INITTYPE_ENUM.ADS_NAME_INITTYPE_GC, "");
			trans.Set((int)ActiveDs.ADS_NAME_TYPE_ENUM.ADS_NAME_TYPE_1779, dn);

			return trans.Get((int)ActiveDs.ADS_NAME_TYPE_ENUM.ADS_NAME_TYPE_NT4);

		}

		/// <summary>
		/// Select a user from the object picker.
		/// </summary>
		private string getUserFromObjectPicker() 
		{
			// ask the user for which the new user is (objectpicker)
			ADObject[] results;
			// OPW from http://dotnet.org.za/armand/articles/2453.aspx
			// Recompiled to default to only prompting for/allowing 'user' type objects.
			ObjectPickerWrapper picker = new ObjectPickerWrapper();
			results = picker.ShowObjectPicker(this.Handle);
			if (results == null)
				return null;

			// there is only one result, because that is all the box will let us
			// get.
			foreach (ADObject res in results)
			{ 
				return ConvertLDAPToNTName(res.UPN);
			}
			return null;
		}


		/// <summary>
		/// Prompt the user for a new owner for a profile, and convert the profile to be usable
		/// by that user.
		/// </summary> 
		private void changeProfileOwner() 
		{
			// this function is the meat of the program.

			string username = profileListView.SelectedItems[0].Text;
			string keyname = username.Replace(@"\", "-");
			string path = profileListView.SelectedItems[0].SubItems[5].Text;
			string oldsid = profileListView.SelectedItems[0].SubItems[6].Text;
			string newusername = null;
			string newsid = null;

			/* Sequence: profile is selected.  a user is selected to receive it.
				 * 
				 * Cases: 
				 * 
				 *  * a ProfileList entry exists, in which case we delete the 
				 *      ProfileList key for the previous owner, and set the ProfilePath
				 *      for the new owner, and we're done.
				 *      In this case, we should probably ask to delete the old data.
				 * 
				 *  * a ProfileList entry does not exist, so this user has not logged in
				 *      before.  We delete the PL key for the previous owner, and 
				 *      create an entry for the new SID 
				 */

			/* at this point we know: the old SID of the profile, which we have to remove
				 */

			newusername = getUserFromObjectPicker();
			if (newusername==null) return;
			
			// find the SIDs associated with the new username

			try 
			{
				newsid = SidTranslator.GetSid(newusername); 
				
			} 
			catch (Exception ex)
			{
				MessageBox.Show("Could not resolve " + username + ".\\nException:" + ex.ToString());
				return;
			}
			

			// don't bother doing anything if you are going to assign the same
			// profile.  (especially, don't delete the old account!)

			if (oldsid == newsid)
			{
				MessageBox.Show("This user already owns this profile. Nothing to do.", "ProfileTool");
				return;
			}

			// if the newsid already has a profile associated with it, ask what we want to do here.

			bool found=false;
			string olddir = "";

			foreach (Profile pr in ProfileList)
			{
				if (pr.SID==newsid)
				{
					found=true;
					olddir = pr.Path;
					break;
				}
			}

			if (found) 
			{
				DialogResult result = MessageBox.Show(String.Format("This will mean user {0} can no longer use the profile at {1}. Are you sure you wish to do this?", newusername, olddir), "ProfileTool", MessageBoxButtons.YesNo);
				if (result.Equals(DialogResult.No)) 
					return;
			}                     
			
			ptFormStatusBar.Text=String.Format("Changing profile owner from {0} to {1}...", username, newusername);

			try 
			{
				// change permission on registry.
				loadThisProfile(path, username);

				try 
				{
					removePermissionFromKey(oldsid, keyname);
					addPermissionToKey(newsid, keyname);
				}
				finally
				{
					unloadThisProfile(path, username);
				}

				// change permission on folder
				removePermissionFromFolder(oldsid, path);
				addPermissionToFolder(newsid, path);

				// fix up the ProfileList key in the registry.
				// if we now have an old profile, offer to delete it.

				if (Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\" + newsid) != null)
				{
					modifyProfileListEntry(newsid, path);
					// ask to delete the old path, at this point, which is no longer relevant
					DialogResult result = MessageBox.Show(String.Format("Delete the old profile data from {0}?", olddir),  "ProfileTool", MessageBoxButtons.YesNo);
					if (result.Equals(DialogResult.Yes))
					{
						try
						{
							removeProfileDirectory(olddir);
						} 
						catch (Exception ex)
						{
							MessageBox.Show("Could not remove " + olddir + ".\\nException:" + ex.ToString());
						}
					}
				}
				else
					renameProfileListEntry(oldsid, newsid, path);
        
				removeProfileListEntry(oldsid);
                
				// profile list is now invalidated
				LoadProfileList();
				DisplayProfileList();

			}
			catch (Exception ex)
			{
				MessageBox.Show("Profile renaming failed: " + ex.ToString());
				ptFormStatusBar.Text="Renaming failed.";
			}

		}

		/// <summary>
		/// Set the SID and ProfileImagePath in a HKLM\..\ProfileList registry key.
		/// </summary>
		private void modifyProfileListEntry(string sid, string path)
		{
			string pl = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\";
			RegistryKey regKey = Registry.LocalMachine.OpenSubKey(pl + sid, true);
			regKey = Registry.LocalMachine.OpenSubKey(pl + sid, true);
			// TODO
			regKey.SetValue("ProfileImagePath", path); //, RegistryValueKind.ExpandString);
			// TODO
			regKey.SetValue("SID", SidTranslator.ConvertStringSidToByteArray(sid)); //, RegistryValueKind.Binary);
			regKey.DeleteValue("GUID", false);
			regKey.Close();
		}

		/// <summary>
		/// Rename a HKLM\..\ProfileList registry key and set the SID and ProfileImagePath in it.
		/// </summary>
		private void renameProfileListEntry(string oldsid, string newsid, string path)
		{           
			string pl = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\";
			RegistryKey regKey = Registry.LocalMachine.OpenSubKey(pl, true);
			RegistryUtils.CopyKey(regKey, oldsid, newsid);
			regKey = Registry.LocalMachine.OpenSubKey(pl + newsid, true);
			//regKey.SetValue("ProfileImagePath", path, RegistryValueKind.ExpandString);
			// TODO
			regKey.SetValue("Sid", SidTranslator.ConvertStringSidToByteArray(newsid)); //, RegistryValueKind.Binary);
			regKey.DeleteValue("GUID", false);
			//regKey.SetValue("State", 4, RegistryValueKind.DWord);
			regKey.Close();
		}

		/// <summary>
		/// Remove a HKLM\..\ProfileList registry key and refresh the list.
		/// </summary>
		private void removeProfileListEntry(string sid)
		{
			string pl = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\";
			RegistryKey regKey = Registry.LocalMachine.OpenSubKey(pl, true);
			regKey.DeleteSubKeyTree(sid);
			regKey.Close();
			
			// this function can invalidate the profile list, so reload it
			LoadProfileList();
			DisplayProfileList();
		}

		/// <summary>
		/// Remove a C:\Documents and Settings\ registry key.
		/// </summary>
		private void removeProfileDirectory(string path)
		{
				Directory.Delete(path, true);
		}

		/// <summary>
		/// Load a profile (a C:\Documents and Settings\username\NTUSER.DAT)
		/// into the registry.
		/// </summary>
		private void loadThisProfile(string path, string username)
		{
			string keyname = username.Replace(@"\", "-");

			RegistryUtils.PrepareRegLoad();
			path = path + @"\ntuser.dat";
			int result = RegistryUtils.RegLoadKey(RegistryUtils.HKEY_USERS, keyname, path);
			if (result != Win32.ERROR_SUCCESS) 
			{
				throw new Win32Exception(result);	
			}
		}

		        
		/// <summary>
		/// Unload a profile (a C:\Documents and Settings\username\NTUSER.DAT)
		/// from the registry.
		/// </summary>
		private void unloadThisProfile(string path, string username)
		{
			string keyname = username.Replace(@"\", "-");

			RegistryUtils.PrepareRegLoad();
			path = path + @"\ntuser.dat";
			int result = RegistryUtils.RegUnLoadKey(RegistryUtils.HKEY_USERS, keyname);
			if (result != Win32.ERROR_SUCCESS) 
			{
				throw new Win32Exception(result);	
			}
		}
        
		/// <summary>
		/// Add permission for a user to access a registry hive.
		/// </summary>
		private void addPermissionToKey(string sid, string keyname)
		{
			RegistryKey rk = Registry.Users.OpenSubKey(keyname);

			if (rk==null)
				throw new ApplicationException("Couldn't open key to add permission");
			try
			{
				// get the security descriptor
				using (SecurityDescriptor sd = SecurityDescriptor.GetRegistryKeySecurity(RegistryUtils.GetRegistryHandle(rk), SECURITY_INFORMATION.DACL_SECURITY_INFORMATION))
				{
					// give the service user standard read access.
					Dacl acl = sd.Dacl;
					IntPtr pSid = new IntPtr();
					SidTranslator.ConvertStringSidToSid(sid, ref pSid);
					Sid serviceUserSID = new Sid(pSid);
					
					acl.RemoveAces(serviceUserSID);
					// interesting note: the rights (second param) sum to 983013
					acl.AddAce(new AceAccessAllowed(serviceUserSID, (AccessType.STANDARD_RIGHTS_REQUIRED | (AccessType)0x3F), AceFlags.CONTAINER_INHERIT_ACE));

					// set the values
					sd.SetDacl(acl);
					sd.SetNamedSecurityInfo(String.Format("USERS\\{0}", keyname), SE_OBJECT_TYPE.SE_REGISTRY_KEY, SECURITY_INFORMATION.DACL_SECURITY_INFORMATION);
				}
			}
			catch (SystemException ex)
			{
				MessageBox.Show(ex.ToString());
			}
			finally
			{
				rk.Close();
			} 
		}

		/// <summary>
		/// Remove permission for a user to access a registry hive.
		/// </summary>
		private void removePermissionFromKey(string sid, string keyname)
		{
			RegistryKey rk = Registry.Users.OpenSubKey(keyname);

			if (rk==null)
				throw new ApplicationException("Couldn't open key to remove permission");

			try
			{
				// get the security descriptor
				using (SecurityDescriptor sd = SecurityDescriptor.GetRegistryKeySecurity(RegistryUtils.GetRegistryHandle(rk), SECURITY_INFORMATION.DACL_SECURITY_INFORMATION))
				{
					Dacl acl = sd.Dacl;
					IntPtr pSid = new IntPtr();
					SidTranslator.ConvertStringSidToSid(sid, ref pSid);
					Sid serviceUserSID = new Sid(pSid);
					

					acl.RemoveAces(serviceUserSID);
					sd.SetDacl(acl);
					sd.SetNamedSecurityInfo(String.Format("USERS\\{0}", keyname), SE_OBJECT_TYPE.SE_REGISTRY_KEY, SECURITY_INFORMATION.DACL_SECURITY_INFORMATION);
				}
			}
			catch (SystemException ex)
			{
				MessageBox.Show(ex.ToString());
			}
			finally
			{
				rk.Close();
			}
		}


		/// <summary>
		/// Add permission for a user to access a profile folder.
		/// </summary>
		private void addPermissionToFolder(string sid, string path)
		{
			// get the security descriptor
			SecurityDescriptor sd = SecurityDescriptor.GetFileSecurity(path, SECURITY_INFORMATION.DACL_SECURITY_INFORMATION);
			Dacl acl = sd.Dacl;

			/* useful code for checking out ACEs on existing objects
					Ace foo;
					for (int i = 0; i < acl.AceCount; i++)
					{
						foo = acl.GetAce(i);
						MessageBox.Show(foo.Sid.AccountName + @"\r\n" + foo.AccessType + @"\r\n" + foo.Flags);
					}
					*/

			IntPtr pSid = new IntPtr();
			SidTranslator.ConvertStringSidToSid(sid, ref pSid);
			Sid serviceUserSID = new Sid(pSid);

			acl.AddAce(new AceAccessAllowed(serviceUserSID, 
				(AccessType) DirectoryAccessType.FILE_ALL_ACCESS, 
				(AceFlags.CONTAINER_INHERIT_ACE | 
				AceFlags.OBJECT_INHERIT_ACE)));
                
			// set the values
			sd.SetDacl(acl);
                
			sd.SetNamedSecurityInfo(path, SE_OBJECT_TYPE.SE_FILE_OBJECT, SECURITY_INFORMATION.DACL_SECURITY_INFORMATION);
		}

		/// <summary>
		/// Remove permission for a user to access a profile folder.
		/// </summary>
		private void removePermissionFromFolder(string sid, string path)
		{
			// get the security descriptor
			SecurityDescriptor sd = SecurityDescriptor.GetFileSecurity(path, SECURITY_INFORMATION.DACL_SECURITY_INFORMATION);
			Dacl acl = sd.Dacl;

			IntPtr pSid = new IntPtr();
			SidTranslator.ConvertStringSidToSid(sid, ref pSid);
			Sid serviceUserSID = new Sid(pSid);
			acl.RemoveAces(serviceUserSID);
                
			// set the values
			sd.SetDacl(acl);
			sd.SetNamedSecurityInfo(path, SE_OBJECT_TYPE.SE_FILE_OBJECT, SECURITY_INFORMATION.DACL_SECURITY_INFORMATION);
		}

		/// <summary>
		/// Fill sizes of profiles (run in a separate thread)
		/// </summary>
		private void FillSizes()
		{
			int index = 0;
			foreach (Profile pr in this.ProfileList)
			{

				// Find the directory size of this path.
				if (Directory.Exists(pr.Path))
				{
					DirectoryInfo d = new DirectoryInfo(pr.Path);
					pr.Size = Profile.DirSize(d);
				}
				SetSize(index, FormatFileSize(pr.Size));
                
				index++;
			}           
		}

		/// <summary>
		/// Thread safe function for setting the size of a profile in the on-screen list
		/// </summary>
		private void SetSize(int index, string text)
		{
			// InvokeRequired required compares the thread ID of the
			// calling thread to the thread ID of the creating thread.
			// If these threads are different, it returns true.
			if (profileListView.InvokeRequired)
			{
				FillSizesCallback d = new FillSizesCallback(SetSize);
				this.Invoke(d, new object[] { index, text });
			}
			else
			{
				if (this.profileListView.Items.Count > index) 
				{
					this.profileListView.Items[index].SubItems[1].Text = text; //size
				}
			}
		}

		/// <summary>
		/// Move a profile on the disk and update the registry to match
		/// </summary>
		private void renameProfilePath()
		{
			string sid = profileListView.SelectedItems[0].SubItems[6].Text;       
			string path = profileListView.SelectedItems[0].SubItems[5].Text;

			// show a dialog to ask for a new path, and return if you don't get a new
			// path

			ptPathEntryBox dlg1 = new ptPathEntryBox();
			dlg1.Path = path;

			dlg1.ShowDialog();
			if (dlg1.DialogResult == DialogResult.Cancel)
				return;
       
			DirectoryInfo dest = new DirectoryInfo(dlg1.Path);
			dlg1.Dispose();

			if (dest.Exists)
			{
				MessageBox.Show("Cannot rename to " + dest.ToString() + "; that directory exists.");
				return;
			}
            
			Directory.Move(path, dest.ToString());
            
			// change the ProfileImagePath to match
            
			string pl = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\";
			RegistryKey regKey = Registry.LocalMachine.OpenSubKey(pl+sid, true);
			// TODO
			regKey.SetValue("ProfileImagePath", dest.ToString()); //, RegistryValueKind.ExpandString);
			regKey.Close();

			// this function can change the profile list
			LoadProfileList();
			DisplayProfileList();
		}

		private void setUserPreference(string sid, Profile.USERPREFERENCE state) 
		{
			string pl = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\";
			RegistryKey regKey = Registry.LocalMachine.OpenSubKey(pl+sid, true);

			try 
			{
				regKey.SetValue("UserPreference", (int)state);
			}
			catch (SystemException ex)
			{
				MessageBox.Show(ex.ToString());
			}
			finally
			{
				regKey.Close();
			}
			
			// this function can change the profile list
			LoadProfileList();
			DisplayProfileList();
		}

		/// <summary>
		/// Catch mouse down on the profile list, so we can only launch the context menu
		/// when you've highlighted a profile.  Also, customize the menu so that you can't
		/// perform actions that aren't valid for the profile you have selected.
		/// </summary>
		private void profileListView_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
		{
			if (e.Button == MouseButtons.Right)
			{
				ListViewItem i = profileListView.GetItemAt(e.X, e.Y);
				
				if (i!=null)
				{
					// mark the item as selected, so you can't left-click one item
					// and then right-click a different one
					i.Selected = true;

					// get the profile that matches this list entry
					int ind = i.Index;
					Profile p = (Profile) ProfileList[ind];
				
					// if the hive for this profile is loaded, enable debug registry 
					// options & unload;
					// if not, grey them out and enable loading this hive.
					if (p.IsHiveLoaded)
					{	
						menuLoadHive.Enabled=false;
						menuAddRegistryPermission.Enabled=true;
						menuRemoveRegistryPermission.Enabled=true;
						menuUnloadHive.Enabled=true;
					}
					else
					{
						menuLoadHive.Enabled=true;
						menuAddRegistryPermission.Enabled=false;
						menuRemoveRegistryPermission.Enabled=false;
						menuUnloadHive.Enabled=false;
					}

					// disable changing between roaming/local
					menuChangeRoamingLocal.Enabled=false;
					menuChangeLocalRoaming.Enabled=false;

					// if we're in Type: Roaming, allow changing to Type: Local
					if (profileListView.SelectedItems[0].SubItems[2].Text == "Roaming")
						menuChangeRoamingLocal.Enabled=true;

					// if we're roamable, and Type: Local, allow changing to Type: Roaming
					if (profileListView.SelectedItems[0].SubItems[2].Text == "Local"
						&& (p.RoamingPath.Length>0))
						menuChangeLocalRoaming.Enabled=true;

					contextMenu1.Show(this, new Point(e.X, e.Y));
				}
			}
		}

		#region Click handlers

		private void menuItemProfileExit_Click(object sender, System.EventArgs e)
		{
			Application.Exit();
		}

		private void menuHelpAbout_Click(object sender, System.EventArgs e)
		{
			ptAboutBox A = new ptAboutBox();
			A.ShowDialog();
		}

		private void menuLoadHive_Click(object sender, System.EventArgs e)
		{
			string path = profileListView.SelectedItems[0].SubItems[5].Text;
			string username = profileListView.SelectedItems[0].Text;
			try 
			{
				loadThisProfile(path, username);		
				int ind = profileListView.SelectedItems[0].Index;
				Profile pr = (Profile) ProfileList[ind];
				pr.IsHiveLoaded=true;
			
			}
			catch (Exception ex) 
			{
				MessageBox.Show("Loading failed: " + ex.ToString());
			}
		}

		private void menuUnloadHive_Click(object sender, System.EventArgs e)
		{
			string path = profileListView.SelectedItems[0].SubItems[5].Text;
			string username = profileListView.SelectedItems[0].Text;
			try
			{
				unloadThisProfile(path, username);		
				int ind = profileListView.SelectedItems[0].Index;
				Profile pr = (Profile) ProfileList[ind];
				pr.IsHiveLoaded=false;
			}
			catch (Exception ex) 
			{
				MessageBox.Show("Unloading failed: " + ex.ToString());
			}
		}

		private void menuRemoveRegistryPermission_Click(object sender, System.EventArgs e)
		{
			string sid = profileListView.SelectedItems[0].SubItems[6].Text;
			string username = profileListView.SelectedItems[0].Text;
			string keyname = username.Replace(@"\", "-");

			removePermissionFromKey(sid, keyname);
		}

		private void menuAddRegistryPermission_Click(object sender, System.EventArgs e)
		{
			string username = profileListView.SelectedItems[0].Text;
			string keyname = username.Replace(@"\", "-");
			string newsid = null;

			string newusername = getUserFromObjectPicker();
			if (newusername==null) return;

			try 
			{
				newsid = SidTranslator.GetSid(newusername); 
				
			} 
			catch (Exception ex)
			{
				MessageBox.Show("Could not resolve " + newusername + ".\\nException:" + ex.ToString());
				return;
			}

			try 
			{
					addPermissionToKey(newsid, keyname);
			}
			catch (Exception ex)
			{
				MessageBox.Show("Adding registry permission failed: " + ex.ToString());
			}
		}

		private void menuRemoveOwnerPermission_Click(object sender, System.EventArgs e)
		{
			string username = profileListView.SelectedItems[0].Text;
			string path = profileListView.SelectedItems[0].SubItems[5].Text;
			string sid = profileListView.SelectedItems[0].SubItems[6].Text;

			try
			{
				removePermissionFromFolder(sid, path);
			}
			catch (Exception ex)
			{
				MessageBox.Show("Removing folder permission failed: " + ex.ToString());
			}
		}

		private void menuAddFolderPermission_Click(object sender, System.EventArgs e)
		{
			string username = profileListView.SelectedItems[0].Text;
			string path = profileListView.SelectedItems[0].SubItems[5].Text;
			string newsid = null;

			string newusername = getUserFromObjectPicker();
			if (newusername==null) 
				return;

			try 
			{
				newsid = SidTranslator.GetSid(newusername); 
				
			} 
			catch (Exception ex)
			{
				MessageBox.Show("Could not resolve " + newusername + ".\\nException:" + ex.ToString());
				return;
			}

			try
			{
				addPermissionToFolder(newsid, path);
			}
			catch (Exception ex)
			{
				MessageBox.Show("Adding file permission failed: " + ex.ToString());
			}         

		}

		private void menuChangeProfileOwner_Click(object sender, System.EventArgs e)
		{
			changeProfileOwner();
		}

		private void menuDeleteFromRegistryOnly_Click(object sender, System.EventArgs e)
		{
			string sid = profileListView.SelectedItems[0].SubItems[6].Text;
			removeProfileListEntry(sid);
		}

		private void menuDeleteFromRegistryAndDisk_Click(object sender, System.EventArgs e)
		{
			string sid = profileListView.SelectedItems[0].SubItems[6].Text;
			removeProfileListEntry(sid);

			string path = profileListView.SelectedItems[0].SubItems[5].Text;
			if (MessageBox.Show("Are you sure you want to delete " + path + "?", "Confirm delete", MessageBoxButtons.YesNo) == DialogResult.Yes)
			{
				removeProfileDirectory(path);
			}	
		}


		private void menuRenameDirectory_Click(object sender, System.EventArgs e)
		{
			renameProfilePath();
		}


		private void menuChangeRoamingLocal_Click(object sender, System.EventArgs e)
		{
			string sid = profileListView.SelectedItems[0].SubItems[6].Text;       
			setUserPreference(sid, Profile.USERPREFERENCE.LOCAL);
		}

		private void menuChangeLocalRoaming_Click(object sender, System.EventArgs e)
		{
			string sid = profileListView.SelectedItems[0].SubItems[6].Text;       
			setUserPreference(sid, Profile.USERPREFERENCE.REMOTE);
		}

		private void menuHelpUsing_Click(object sender, System.EventArgs e)
		{
			System.Diagnostics.Process.Start("readme.txt");
		}

		#endregion

		private void profileListView_ColumnClick(object sender, System.Windows.Forms.ColumnClickEventArgs e)
		{
			// Determine if clicked column is already the column that is being sorted.
			if ( e.Column == lvwColumnSorter.SortColumn )
			{
				// Reverse the current sort direction for this column.
				if (lvwColumnSorter.Order == SortOrder.Ascending)
				{
					lvwColumnSorter.Order = SortOrder.Descending;
				}
				else
				{
					lvwColumnSorter.Order = SortOrder.Ascending;
				}
			}
			else
			{
				// Set the column number that is to be sorted; default to ascending.
				lvwColumnSorter.SortColumn = e.Column;
				lvwColumnSorter.Order = SortOrder.Ascending;
			}

			// Perform the sort with these new sort options.
			this.profileListView.Sort();
		}

	}
}
